package com.simple.trade.service.vm.impl;

import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Preconditions;
import com.google.common.base.Suppliers;
import com.google.common.base.Throwables;
import com.google.common.base.Verify;
import com.simple.common.entity.ReqContext;
import com.simple.common.entity.ResData;
import com.simple.common.entity.constant.Payment;
import com.simple.common.entity.dto.pay.*;
import com.simple.common.entity.po.Notify;
import com.simple.common.entity.po.NotifyChannel;
import com.simple.common.entity.po.sys.*;
import com.simple.common.framework.scale.Scale;
import com.simple.ds.service.*;
import com.simple.trade.holder.PayContext;
import com.simple.trade.service.vm.DistributeService;
import com.simple.trade.service.vm.NotifyService;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.math.BigDecimal;
import java.util.*;

/**
 * @author yasud
 * @date 2020年10月12日 10:52
 * @description
 */
@Service
@Slf4j
public class DistributeServiceImpl implements DistributeService {

    @Autowired
    ChannelService channelService;

    @Autowired
    NotifyChannelService notifyChannelApi;

    @Autowired
    TradeApi tradeApi;

    @Autowired
    RefundTradeApi refundTradeApi;

    @Autowired
    RefundTradeInventoryApi refundTradeInventoryApi;

    @Autowired
    ObjectMapper objectMapper;

    @Autowired
    NotifyService notifyService;

    /**
     * 分发
     * <p>
     * 接收
     * |
     * 渠道服务返回
     * |
     * 回调商户
     *
     * @param notifyChannel
     */
    @Override
    public void distribute(NotifyChannel notifyChannel) throws JsonProcessingException {
        ChannelComp channelComp = channelService.findChannelCompByChannelId(notifyChannel.getChannelId());
        if (!notifyChannel.getIsRefund()) {
            distributeTrade(notifyChannel, channelComp);
        } else {
            //这里还有退款回调
            distributeRefund(notifyChannel, channelComp);
        }
    }

    /**
     * 分发退款订单
     *
     * @param notifyChannel
     * @param channel
     */
    @SneakyThrows
    private void distributeRefund(NotifyChannel notifyChannel, ChannelComp channel) {
        NotifyChannel.Type type = notifyChannel.getType();
        if (type == NotifyChannel.Type.FIX) {
            CallbackFixRefundTradeDto callbackFixRefundTradeDto = CallbackFixRefundTradeDto.generateCallbackDto(notifyChannel.getBody(),
                    objectMapper.readValue(notifyChannel.getParam(), Map.class),
                    objectMapper.readValue(notifyChannel.getHeader(), Map.class),
                    notifyChannel.getCreateTime(),
                    channel,
                    null,
                    null);
            ResData<CallbackFixRefundTradeRspDto> callbackFixRefundTradeRspDtoResData = null;
            if (Objects.isNull(notifyChannel.getRefundTradeInventoryId())) {
                callbackFixRefundTradeRspDtoResData = forwardToChannel(callbackFixRefundTradeDto, channel, Payment.CALLBACK_FIX_REFUND_TRADE_CODE, CallbackFixRefundTradeRspDto.class);
            }
            else {
                CallbackFixRefundTradeRspDto tdto = new CallbackFixRefundTradeRspDto();
                tdto.setRefundTradeInventoryId(notifyChannel.getRefundTradeInventoryId());
                callbackFixRefundTradeRspDtoResData = ResData.ok(tdto);
            }

            // 可能存在推送退款但是并没有经过 请求退款 的接口
            if (callbackFixRefundTradeRspDtoResData.isOk()) {
                Long refundTradeInventoryId = callbackFixRefundTradeRspDtoResData.getData().getRefundTradeInventoryId();
                RefundTradeInventory refundTradeInventory;
                if (Objects.isNull(refundTradeInventoryId)) {
                    CallbackFixRefundTradeRspDto data = callbackFixRefundTradeRspDtoResData.getData();
                    Preconditions.checkState(StringUtils.isNotBlank(data.getChannelRefundTradeNo()), "在未获得平台退款id的情况下，必须返回渠道退款id");
                    List<RefundTradeInventory> refundTrades = refundTradeInventoryApi.findByChannelRefundNo(data.getChannelRefundTradeNo());
                    if (CollectionUtils.isNotEmpty(refundTrades) && refundTrades.size() > 1) {
                        log.info("注意渠道退款订单{}查询出大于一条, 系统默认选择最新一条", data.getChannelRefundTradeNo());
                    }
                    refundTradeInventory = refundTrades.stream().sorted(Comparator.comparing(BaseEntity2::getCreateTime).reversed()).findFirst().orElse(null);
                    Preconditions.checkNotNull(refundTradeInventory, "找不到渠道退款id{}的退款", data.getChannelRefundTradeNo());
                }
                else {
                    refundTradeInventory = refundTradeInventoryApi.findById(refundTradeInventoryId);
                    Verify.verifyNotNull(refundTradeInventory, "退款流水%s无效", refundTradeInventoryId);
                }
                if (refundTradeInventory.getStatus() == RefundTradeInventory.Status.SUCCESS) {
                    log.info("退款{}已经成功，退出", refundTradeInventory.getId());
                    return;
                }
                RefundTrade refundTrade = refundTradeApi.findByTradeId(refundTradeInventory.getRefundTradeId());
                Trade trade = tradeApi.findById_(refundTrade.getTradeId());

                PayContext.current().iam(trade);
                channel = PayContext.current().channel();
                Product product = PayContext.current().product();
                ReqContext context = PayContext.current().getContext();
                CallbackFixRefundDto callbackFixRefundDto = CallbackFixRefundDto.generateCallbackDto(notifyChannel.getBody(),
                        objectMapper.readValue(notifyChannel.getParam(), Map.class),
                        objectMapper.readValue(notifyChannel.getHeader(), Map.class),
                        notifyChannel.getCreateTime(),
                        channel,
                        context,
                        product,
                        trade, refundTrade, refundTradeInventory);

                ResData<CallbackRefundRspDto> callbackRefundRspDtoResData = forwardToChannel(callbackFixRefundDto, channel, Payment.CALLBACK_FIX_REFUND_CODE, CallbackRefundRspDto.class);
                if (callbackRefundRspDtoResData.isOk()) {
                    CallbackRefundRspDto callbackRefundRspDto = callbackRefundRspDtoResData.getData();
                    RefundTradeInventory inventory = callbackRefundRspDto.getRefundTradeInventory();
                    Preconditions.checkNotNull(inventory, "退款交易不能为空!!");
                    //只有确定退款完成之后才能做操作
                    if (inventory.getStatus() == RefundTradeInventory.Status.SUCCESS) {
                        log.info("商户退款{}退款成功 ", inventory.getId());
                        inventory.setRefundTime(new Date());
                        refundTrade.addRefundAmount(inventory.getRefundAmount());
                        refundTrade.addRealRefundAmount(inventory.getRealRefundAmount());
                        trade.addRefundAmount(inventory.getRefundAmount());
                        trade.setRefundStatus(isSufficientRefundAmount(trade, new BigDecimal(inventory.getRefundAmount())) ? Trade.RefundStatus.REFUND_PART : Trade.RefundStatus.REFUND_FULL);

                        tradeApi.updateById_(trade);
                        refundTradeApi.updateById_(refundTrade);
                        refundTradeInventoryApi.updateById_(inventory);
                    } else if (inventory.getStatus() == RefundTradeInventory.Status.PENDING || inventory.getStatus() == RefundTradeInventory.Status.ACCEPT) {
                        log.info("商户退款{}退款处理中", inventory.getId());
                    } else {
                        log.info("商户退款{}退款失败, 返回结果", inventory.getId());
                    }
                    notifyChannel.setRefundTradeInventoryId(refundTradeInventoryId);
                    notifyChannel.setTradeId(trade.getId());
                    notifyChannel.setAppId(trade.getAppId());

                    notifyChannelApi.updateById_(notifyChannel);
                } else {
                    log.info("获取流水{}回调渠道服务时出现异常{}", refundTradeInventoryId, callbackRefundRspDtoResData.getMsg());
                }
            } else {
                log.info("渠道通知{}获取退款流水时异常渠道返回{}", notifyChannel.getId(), callbackFixRefundTradeRspDtoResData.getMsg());
            }
        } else {
            RefundTradeInventory refundTradeInventory = refundTradeInventoryApi.findById(notifyChannel.getRefundTradeInventoryId());
            Verify.verifyNotNull(refundTradeInventory, "退款流水%s无效", notifyChannel.getRefundTradeInventoryId());
            if (refundTradeInventory.getStatus() == RefundTradeInventory.Status.SUCCESS) {
                log.info("退款{}已经成功，退出", refundTradeInventory.getId());
                return;
            }
            RefundTrade refundTrade = refundTradeApi.findByTradeId(refundTradeInventory.getRefundTradeId());
            Trade trade = tradeApi.findById_(refundTrade.getTradeId());
            PayContext.current().iam(trade);
            channel = PayContext.current().channel();
            Product product = PayContext.current().product();
            ReqContext context = PayContext.current().getContext();

            CallbackDynamicRefundDto callbackDynamicRefundDto = CallbackDynamicRefundDto.generateCallbackDto(notifyChannel.getBody(),
                    objectMapper.readValue(notifyChannel.getParam(), Map.class),
                    objectMapper.readValue(notifyChannel.getHeader(), Map.class),
                    notifyChannel.getCreateTime(),
                    channel,
                    context,
                    product,
                    trade, refundTrade, refundTradeInventory);
            ResData<CallbackRefundRspDto> callbackRefundRspDtoResData = forwardToChannel(callbackDynamicRefundDto, channel, Payment.CALLBACK_DYNAMIC_REFUND_CODE, CallbackRefundRspDto.class);
            if (callbackRefundRspDtoResData.isOk()) {
                CallbackRefundRspDto callbackRefundRspDto = callbackRefundRspDtoResData.getData();
                RefundTradeInventory inventory = callbackRefundRspDto.getRefundTradeInventory();
                Preconditions.checkNotNull(inventory, "退款交易不能为空!!");
                //只有确定退款完成之后才能做操作
                if (inventory.getStatus() == RefundTradeInventory.Status.SUCCESS) {
                    log.info("商户退款{}退款成功 ", inventory.getId());
                    inventory.setRefundTime(new Date());
                    refundTrade.addRefundAmount(inventory.getRefundAmount());
                    refundTrade.addRealRefundAmount(inventory.getRealRefundAmount());
                    trade.setRefundStatus(getRefundedStatus(trade, new BigDecimal(inventory.getRefundAmount())));
                    trade.addRefundAmount(inventory.getRefundAmount());
                } else if (inventory.getStatus() == RefundTradeInventory.Status.PENDING || inventory.getStatus() == RefundTradeInventory.Status.ACCEPT) {
                    log.info("商户退款{}退款处理中", inventory.getId());
                } else {
                    log.info("商户退款{}退款失败, 返回结果", inventory.getId());
                }
                tradeApi.updateById_(trade);
                refundTradeApi.updateById_(refundTrade);
                refundTradeInventoryApi.updateById_(inventory);
            } else {
                log.info("获取流水{}回调渠道服务时出现异常{}", refundTradeInventory.getId(), callbackRefundRspDtoResData.getMsg());
                refundTradeInventory.setStatus(RefundTradeInventory.Status.FAILED);
                refundTradeInventoryApi.updateById_(refundTradeInventory);
            }
        }
    }

    /**
     * 是否有足够的退款金额
     *
     * @param trade
     * @param refundAmount
     * @return
     */
    private boolean isSufficientRefundAmount(Trade trade, BigDecimal refundAmount) {
        return Scale.scale( new BigDecimal(trade.getAmount()))
                .subtract(Scale.scale(new BigDecimal(trade.getRefundAmount()))).compareTo(refundAmount) >= 0;
    }

    /**
     * 获取退款状态
     *
     * @param trade
     * @param refundAmount
     * @return
     */
    private Trade.RefundStatus getRefundedStatus(Trade trade, BigDecimal refundAmount) {
        BigDecimal v1 = new BigDecimal(trade.getAmount());
        BigDecimal v2 = new BigDecimal(trade.getRefundAmount());
        if (Scale.scale(v1.subtract(v2)).compareTo(Scale.scale(refundAmount)) == 0) {
            return Trade.RefundStatus.REFUND_FULL;
        } else if (Scale.scale(v1.subtract(v2)).compareTo(Scale.scale(refundAmount)) >= 1) {
            return Trade.RefundStatus.REFUND_PART;
        } else {
            Preconditions.checkState(false, "退款无效");
            return null;
        }
    }

    /**
     * 分发订单
     *
     * @param notifyChannel
     * @param channel
     * @throws JsonProcessingException
     */
    private void distributeTrade(NotifyChannel notifyChannel, ChannelComp channel) throws JsonProcessingException {
        NotifyChannel.Type type = notifyChannel.getType();

        ResData<CallbackRspDto> callbackRspDtoResData;
        if (NotifyChannel.Type.FIX == type) {

            CallbackFixTradeDto callbackFixTradeDto = CallbackFixTradeDto.generateCallbackDto(notifyChannel.getBody(),
                    objectMapper.readValue(notifyChannel.getParam(), Map.class),
                    objectMapper.readValue(notifyChannel.getHeader(), Map.class),
                    notifyChannel.getCreateTime(),
                    channel,
                    null,
                    null);
            ResData<CallbackFixTradeRspDto> rspDtoResData = null;
            if (Objects.isNull(notifyChannel.getTradeId())) {
                rspDtoResData = forwardToChannel(callbackFixTradeDto, channel, Payment.CALLBACK_FIX_TRADE_CODE, CallbackFixTradeRspDto.class);
            }
            else {
                CallbackFixTradeRspDto tdto = new CallbackFixTradeRspDto();
                tdto.setTradeNo(notifyChannel.getTradeId());
                rspDtoResData = ResData.ok(tdto);
            }
            if (rspDtoResData.isOk()) {
                CallbackFixTradeRspDto rspDto = rspDtoResData.getData();
//                Preconditions.checkArgument(Objects.nonNull(rspDto.getTradeNo()), "交易id不能为空");
                Trade trade;
                if (Objects.isNull(rspDto.getTradeNo())) {
                    log.info("固定回调渠道返回交易id为空查看返回渠道交易是否为空");
                    Preconditions.checkState(StringUtils.isNotBlank(rspDto.getChannelTradeNo()), "在未返回平台交易id的情况下,必须返回渠道id");
                    List<Trade> trades = tradeApi.findTradeByChannelOrderNo(rspDto.getChannelTradeNo());
                    if (CollectionUtils.isNotEmpty(trades) && trades.size() > 1) {
                        log.info("注意渠道订单{}查询出大于一条, 系统默认选择最新一条", rspDto.getChannelTradeNo());
                    }
                    trade = trades.stream().sorted(Comparator.comparing(BaseEntity2::getCreateTime).reversed()).findFirst().orElse(null);
                    Preconditions.checkNotNull(trade, "找不到渠道交易id{}的交易", rspDto.getChannelTradeNo());
                }
                else {
                    trade = tradeApi.findById_(rspDto.getTradeNo());
                }
                Preconditions.checkNotNull(trade, "交易id" + rspDto.getTradeNo() + "无效");
                PayContext.current().iam(trade);
                channel = PayContext.current().channel();
                Product product = PayContext.current().product();
                ReqContext context = PayContext.current().getContext();

                if (notifyService.countNotify(notifyChannel.getTradeId()) == 0) {
                    CallbackFixDto callbackDto = CallbackFixDto.generateCallbackDto(notifyChannel.getBody(),
                            objectMapper.readValue(notifyChannel.getParam(), Map.class),
                            objectMapper.readValue(notifyChannel.getHeader(), Map.class),
                            notifyChannel.getCreateTime(),
                            channel,
                            context,
                            product,
                            trade);
                    callbackRspDtoResData = forwardToChannel(callbackDto, channel, Payment.CALLBACK_FIX_CODE, CallbackRspDto.class);

                    if (callbackRspDtoResData.isOk()) {
                        log.info("交易{}转发回调给{}成功{}", rspDto.getTradeNo(), channel.getServiceProvider(), objectMapper.writeValueAsString(callbackRspDtoResData));
                        CallbackRspDto data = callbackRspDtoResData.getData();
                        Preconditions.checkNotNull(data.getTrade(), "返回交易不能为空!!");
                        if (data.getTrade().getStatus() == Trade.Status.SUCCESS) {
                            log.info("交易{}成功", rspDto.getTradeNo());

                            Boolean isSendMerchant = notifyChannel.getIsSendMerchant();
                            if (isSendMerchant) {
                                Notify notify = notifyService.createNotify(notifyChannel, data.getTrade());
                            }
                            //下面更新为已处理
                            if (Objects.isNull(data.getTrade().getCompleteTime())) {
                                data.getTrade().setCompleteTime(new Date());
                            }
                            notifyChannel.setTradeId(trade.getId());
                            notifyChannel.setAppId(trade.getAppId());
                            notifyChannel.setIsProcessed(true);
                            notifyChannelApi.updateById_(notifyChannel);
                        } else if (data.getTrade().getStatus() == Trade.Status.FAILED) {
                            log.info("交易{}失败", rspDto.getTradeNo());
                            Boolean isSendMerchant = notifyChannel.getIsSendMerchant();
                            if (isSendMerchant) {
                                Notify notify = notifyService.createNotify(notifyChannel, data.getTrade());
                            }
                            data.getTrade().setCompleteTime(new Date());
                            //下面更新为已处理
                            notifyChannel.setTradeId(trade.getId());
                            notifyChannel.setAppId(trade.getAppId());
                            notifyChannel.setIsProcessed(true);
                            notifyChannelApi.updateById_(notifyChannel);
                        } else {
                            log.info("交易{}处理中", rspDto.getTradeNo());
                        }
                        tradeApi.updateById_(data.getTrade());
                    } else {
                        log.info("交易{}回调渠道服务时出现异常{}", rspDto.getTradeNo(), callbackRspDtoResData.getMsg());
                    }
                } else {
                    log.info("订单{}已存在回调， 忽略", trade.getId());
                }
            } else {
                log.info("渠道通知{}获取交易id时异常渠道返回{}", notifyChannel.getId(), rspDtoResData.getMsg());
            }
        } else {
            Trade trade = tradeApi.findById_(notifyChannel.getTradeId());
            Verify.verifyNotNull(trade, "交易id%s无效", notifyChannel.getTradeId());
            PayContext.current().iam(trade);
            channel = PayContext.current().channel();
            Product product = PayContext.current().product();
            ReqContext context = PayContext.current().getContext();

            //幂等
            if (notifyService.countNotify(notifyChannel.getTradeId()) == 0) {
                CallbackDynamicDto callbackDynamicDto = CallbackDynamicDto.generateCallbackDto(notifyChannel.getBody(),
                        objectMapper.readValue(notifyChannel.getParam(), Map.class),
                        objectMapper.readValue(notifyChannel.getHeader(), Map.class),
                        notifyChannel.getCreateTime(),
                        channel,
                        context,
                        product, trade);
                callbackRspDtoResData = forwardToChannel(callbackDynamicDto, channel, Payment.CALLBACK_DYNAMIC_CODE, CallbackRspDto.class);

                if (callbackRspDtoResData.isOk()) {
                    log.info("交易{}转发回调给{}成功{}", trade.getId(), channel.getServiceProvider(), objectMapper.writeValueAsString(callbackRspDtoResData));
                    CallbackRspDto rspDto = callbackRspDtoResData.getData();
                    Preconditions.checkNotNull(rspDto.getTrade(), "返回交易不能为空!!");
                    if (rspDto.getTrade().getStatus() == Trade.Status.SUCCESS) {
                        log.info("交易{}成功", trade.getId());
                        Boolean isSendMerchant = notifyChannel.getIsSendMerchant();
                        if (isSendMerchant) {
                            Notify notify = notifyService.createNotify(notifyChannel, rspDto.getTrade());
                        }
                        if (Objects.isNull(rspDto.getTrade().getCompleteTime())) {
                            rspDto.getTrade().setCompleteTime(new Date());
                        }
                        //下面更新为已处理
                        notifyChannel.setIsProcessed(true);
                        notifyChannelApi.updateById_(notifyChannel);
                    } else if (rspDto.getTrade().getStatus() == Trade.Status.FAILED) {
                        log.info("交易{}失败", trade.getId());
                        Boolean isSendMerchant = notifyChannel.getIsSendMerchant();
                        if (isSendMerchant) {
                            Notify notify = notifyService.createNotify(notifyChannel, rspDto.getTrade());
                        }
                        rspDto.getTrade().setCompleteTime(new Date());
                        //下面更新为已处理
                        notifyChannel.setIsProcessed(true);
                        notifyChannelApi.updateById_(notifyChannel);
                    } else {
                        log.info("交易{}处理中", trade.getId());
                    }
                    tradeApi.updateById_(rspDto.getTrade());
                } else {
                    log.info("交易{}转发回调给{}返回异常{}", trade.getId(), channel.getServiceProvider(), callbackRspDtoResData.getMsg());
                }
            } else {
                log.info("订单{}已存在回调， 忽略", trade.getId());
            }
        }
    }


    /**
     * 根据notifyChannel 的id 进行分发
     *
     * @param notifyChannelId
     * @throws JsonProcessingException
     */
    @Override
    public void distribute(Long notifyChannelId) throws JsonProcessingException {
        NotifyChannel notifyChannel = notifyChannelApi.findById_(notifyChannelId);
        distribute(notifyChannel);
    }



    @Override
    public String send(Long tradeNo) {
        Notify notify =notifyService.getNotifyByTradeNo(tradeNo);
        Preconditions.checkNotNull(notify,"订单通知不存在");
        try {
            HttpRequest post = HttpUtil.createPost(notify.getNotifyUrl());
            post.contentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
            post.body(notify.getMessage());

            HttpResponse rsp = post.execute();
           String body = rsp.body();
            log.info("订单[{}]重发通知{}返回{}", notify.getTradeNo(), "成功", body);
            return body;
        } catch (Exception ex) {
            log.info("订单[{}]重发通知{}原因为：{}", notify.getTradeNo(), "异常", ex.getMessage());
            return ex.getMessage();
        }
    }


    @Autowired
    LoadBalancerClient loadBalancerClient;

    @Autowired
    RestTemplate restTemplate;

    /**
     * 这里用rest方式最简单，
     * 内部接口相互通信全部使用post
     *
     * @param directPayDto
     * @param channel
     */
    @SneakyThrows
    public <R> ResData<R> forwardToChannel(BaseDto directPayDto, ChannelComp channel, String type, Class<R> responseClass) {
        log.info("请求渠道{}开始参数为{}", channel.getCode(), objectMapper.writeValueAsString(directPayDto));
        ServiceInstance instance = loadBalancerClient.choose(channel.getServiceProvider());
        Preconditions.checkNotNull(instance, "The service is starting. Please try again later");
        ResData resData = Suppliers.memoize(() -> {
            try {
                ResponseEntity<String> responseEntity = restTemplate.postForEntity(getServiceUrl(instance, type), directPayDto, String.class);
                ResData rResData = objectMapper.readValue(responseEntity.getBody(), ResData.class);
                return rResData;
            } catch (Exception ex) {
                ex.printStackTrace();
                return ResData.error(Throwables.getRootCause(ex).getMessage());
            }
        }).get();
        R r = objectMapper.readValue(objectMapper.writeValueAsString(resData.getData()), responseClass);
        resData.setData(r);
        log.info("请求渠道{}结束返回{}", channel.getCode(), objectMapper.writeValueAsString(resData));
        return resData;
    }

    private String getServiceUrl(ServiceInstance instance, String type) {
        return "http://" + instance.getServiceId() + "/" + type;
    }
}
